# coding: utf-8

import os
import time
import urllib
import hashlib
from collections import defaultdict
from math import ceil, floor

import qiniu
import yagmail
from redis import StrictRedis
from PIL import Image
from sqlalchemy import create_engine, MetaData, func
from sqlalchemy.orm import sessionmaker, mapper

from common.constant import QINIU_APP_ID, QINIU_SECRET_KEY, QINIU_BUCKET_NAME, QINIU_DOMAIN
from common.import_fields import ImportField
from common.log import Log


redis_client = StrictRedis(
    host=os.getenv('redis_host') or 'localhost',
    db=os.getenv('redis_db') or 0,
    password=os.getenv('redis_pwd') or None
)

VALIDATE_TAG = "VALIDATE"

# 创建对象的基类:

engine = create_engine(
    os.getenv('DATABASE_URL')
    or 'mysql+pymysql://root:@127.0.0.1/wph_www_prod?charset=utf8',
    pool_size=20,
    pool_recycle=3600
)

yag_client = yagmail.SMTP(
    user={'data@wanpinghui.com': ''},
    password='wpsd-151217',
    host='smtp.mxhichina.com',
    port=25, smtp_ssl=False
)


class DB(object):
    """
    created by chenhj at 2017/7/31
    db
    """
    def __init__(self, session=None):

        if session is None:
            Session = sessionmaker(bind=engine)
            self.session = Session()
            # 是否需要关闭session
            # 解决 is not bound to a Session;
            # attribute refresh operation cannot proceed
            self.close = True
        else:
            self.close = False
            self.session = session

    def __enter__(self):
        return self.session

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.close:
            try:
                # exit时没有抛异常才进行commit
                if exc_type is None:
                    self.session.commit()
            except Exception as e:
                Log.info('commit error: {}'.format(repr(e)))
                self.session.rollback()
            finally:
                # 无论如何, 关闭此session
                if self.session:
                    self.session.close()


def validates(attr):
    """
    create by ld_wu
    add tag to validates function
    """
    def wrapper(func):
        func.__tag__ = VALIDATE_TAG
        func.__field__ = attr
        return func
    return wrapper


class SchemaMeta(type):

    def __new__(cls, name, bases, attr):
        validators = defaultdict(list)
        fields = set()
        new_cls = type.__new__(cls, name, bases, attr)
        for mro_cls in new_cls.__mro__[::-1]:
            for attr_name, attr_value in mro_cls.__dict__.items():
                if (hasattr(attr_value, '__tag__') and
                        attr_value.__tag__ == VALIDATE_TAG and
                        callable(attr_value)):
                    validators[attr_value.__field__].append(attr_value)
                elif isinstance(attr_value, ImportField):
                    fields.add(attr_value)
                    attr_value.field_name = attr_name
        new_cls.validators = validators
        new_cls.fields = fields
        return new_cls


class ImportSchema(metaclass=SchemaMeta):
    """
    create by ld_wu 2017-04-07
    导入模板模型定义
    """
    # __metaclass__ = SchemaMeta

    def __init__(self, sheet, row, error_msg=None):
        self.sheet = sheet
        self.row = row
        self.error_msg = error_msg or set()
        self.data = self.to_dict()

    def to_dict(self):
        data = dict()
        for field in self.fields:
            data[field.field_name] = getattr(self, field.field_name)
        return data


def generate_mengwang_pwd(userid, pwd, timestamp):
    """
    add by zhouhl 2017-07-25
    对梦网的用户密码加密
    """
    constant_string = '00000000'
    tmp_string = ''.join([userid.upper(), constant_string, pwd, timestamp])

    md5 = hashlib.md5()
    md5.update(tmp_string.encode())
    return md5.hexdigest()


class ImageUtils(object):
    """
    create by ld_wu 2017-12-07
    图片处理
    """

    @staticmethod
    def resize(path, size=200):
        """
        create by ld_wu 2017-12-07
        图片等比缩放，长边为size，短边根据比例缩放
        """
        img = Image.open(path)
        benchmark = img.height if img.height >= img.width else img.width
        if benchmark <= size:
            return img
        new_height = int(img.height / benchmark * size)
        new_width = int(img.width / benchmark * size)
        img.thumbnail((new_width, new_height), Image.BICUBIC)
        return img

    @staticmethod
    def is_image_type(ext):
        return ext.lower() in ('jpg', 'png', 'jpeg', 'gif', 'bmp')


class _ModelBase(object):

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

    @classmethod
    def create(cls, session, **kwargs):
        """
        create by ld_wu 2018-01-25
        """
        _new = cls(**kwargs)
        now = int(time.time())
        _new.created_at = getattr(_new, 'created_at', None) or now
        _new.updated_at = getattr(_new, 'updated_at', None) or now
        _new.deleted_at = getattr(_new, 'deleted_at', None) or 0
        session.add(_new)
        session.flush()
        return _new

    @classmethod
    def get_by_id(cls, session, record_id):
        return session.query(cls).get(record_id)

    @classmethod
    def get_by_ids(cls, session, ids):
        """
        create by ld_wu 2018-01-25
        """
        return session.query(cls).filter(
            cls.id.in_(ids)
        ).all()

    @classmethod
    def get_by_args(cls, session, **kwargs):
        return session.query(
            cls
        ).filter_by(
            **kwargs
        ).first()

    @classmethod
    def get_list_by_args(cls, session, **kwargs):
        return session.query(
            cls
        ).filter_by(
            **kwargs
        ).all()

    def update(self, session, **kwargs):
        """
        create by ld_wu 2018-01-25
        获取实例后更新该实例属性
        :param session:
        :param kwargs:
        :return:
        """
        for key, value in kwargs.items():
            setattr(self, key, value)
        now = int(time.time())
        setattr(self, 'updated_at', now)
        session.flush()

    @classmethod
    def update_by_condition(cls, session, condition, **kwargs):
        """
        create by ld_wu 2018-01-25
        执行符合条件的更新
        """
        return session.query(
            cls
        ).filter_by(
            **condition
        ).update(
            kwargs
        )


class QiNiu(object):
    """
    create by ld_wu 2018-03-07
    七牛云存储工具类
    """

    def __init__(
        self,
        access_key=QINIU_APP_ID,
        secret_key=QINIU_SECRET_KEY,
        bucket_name=QINIU_BUCKET_NAME,
        domain=QINIU_DOMAIN
    ):
        self.auth = qiniu.Auth(access_key, secret_key)
        self.bucket_name = bucket_name
        self.domain = domain

    @property
    def access_token(self):
        return self.auth.upload_token(self.bucket_name)

    def upload(self, key, content):
        token = self.access_token
        ret, info = qiniu.put_data(token, key, content)
        # print(ret, info)
        return self.url(key)

    def url(self, key):
        """
        created by chenhj at 2018/5/22
        NOTE: 可在url后面加上?imageView2/2/w/200/h/200 表示取一个200＊200的图
        """
        return urllib.parse.urljoin(self.domain, key)


class ModelMaker(object):

    @classmethod
    def get_model(cls, table_name, base=None):
        """
        Return class reference mapped to table.
        :param table_name: String with name of table.
        :param base:
        :return: Class reference or None.
        """
        # NOTE engine不能使用全局变量engine
        # 否则会触发Mysql server has gone away的错误
        engine = create_engine(
            os.getenv('DATABASE_URL')
            or 'mysql+pymysql://root:@127.0.0.1/wph_www_prod?charset=utf8',
            pool_recycle=3600
        )
        metadata = MetaData()
        metadata.reflect(bind=engine)
        bases = (_ModelBase, base) if base else (_ModelBase, )
        model = type(table_name, bases, {})
        table = metadata.tables.get(table_name)
        if table is not None:
            mapper(model, table)
            return model
        # raise TableNotExisted('table not existed, declare model failed')


def cal_user_training_index(session, user):
    """
    created by chenhj at 07/02/2018
    计算用户的养成指数
    """
    from .models import model_factory
    total_pay = session.query(model_factory.bill).filter(
        model_factory.bill.uid == user.id,
        model_factory.bill.type == 1,
        model_factory.bill.sub_type.in_([2, 3]),
        model_factory.bill.status == 200
    ).with_entities(func.sum(model_factory.bill.amt)).first()
    total_pay = total_pay[0] or 0
    total_partic = session.query(model_factory.partic).filter(
        model_factory.partic.supplier_id == user.id,
        model_factory.partic.status.in_([110, 120, 130, 150, 200])
    ).count()
    return ceil(float(total_pay)/90) - floor(float(total_partic)/30) * 30 + 30
